﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using System.Reflection;

namespace DailyFx.UI
{
	/// <summary>
	///		Messaging Mediator .
	/// </summary>
    public sealed class Mediator 
    {
        /// <summary>
        ///		all registered handlers .
        /// </summary>
        private readonly Dictionary<string, List<WeakAction>> _handlers ;
        
        
		/// <summary>
		///		the factory creating general background task .
		/// </summary>
		private readonly BackgroundTaskManager _backgroundManager;

		/// <summary>
		/// 
		/// </summary>
		private readonly double _budget;

		/// <summary>
		///		the factory creating task on ui thread .
		/// </summary>
        private readonly LongRunningTaskManager _longRunningManager;

		/// <summary>
		///		the scheduler of managing pre-emptive tasks .
		/// </summary>
		private readonly TaskManager _taskManager;

		/// <summary>
		///		the singleton instance for messaging mediator .
		/// </summary>
        private static readonly Mediator singleton = new Mediator ();


		/// <summary>
		/// 
		/// </summary>
        private Mediator ()
        {
			this._handlers = new Dictionary<string, List<WeakAction>> ();

			this._backgroundManager = new BackgroundTaskManager() ;
			this._budget = 50; 
			//
            this._longRunningManager = new LongRunningTaskManager( ApartmentState.STA );
            //
			this._taskManager = new TaskManager ();
        }

		/// <summary>
		///		Gets the singleton of this mediator .
		/// </summary>
        public static Mediator Singleton
        {
            get { return singleton; }
        }

		/// <summary>
		///		Broadcasts the message of specified key id .
		/// </summary>
        public bool Broadcast ( string key, params object[] message )
        {
            List<WeakAction> was ;
            lock ( this._handlers )
            {
                if ( !this._handlers.TryGetValue(key, out was) )
                    return false;
            }

            foreach ( var wa in was )
            {
                Delegate action = wa.GetMethod();
                if ( action == null ) 
					continue;

                switch ( wa.TaskMode )
                {
                    case TaskKind.Background:
                        // check if already running
						this._backgroundManager.RunTask( wa.Target.Target, action, message );
                        break;

                    case TaskKind.Periodic:
						this._taskManager.RunTask( action, message, TaskKind.Periodic, this._budget);
                        break;

                    case TaskKind.Sporadic:
                        // one periodic to run all sporadic(s)
						this._taskManager.RunTask( action, message, TaskKind.Sporadic, this._budget);
                        break;

                    case TaskKind.LongRunning:
                        //UI
                        this._longRunningManager.RunTask( action, message );
                        break;
                }

            }

            lock (_handlers)
            {
                was.RemoveAll(wa => wa.HasBeenCollected);
            }

            return true;
        }

        public void Register<T> ( string key, Action<T> handler, TaskKind taskMode)
        {
            register ( key, handler.GetType(), handler, taskMode );
        }

        public void Unregister<T> ( string key, Action<T> handler )
        {
            unregister ( key, handler.GetType(), handler );
        }

        public void Register ( object @object )
        {
            // Look at all instance/static methods on this object type.
            foreach ( var mi in @object.GetType().GetMethods(
											BindingFlags.Instance | BindingFlags.Static |
											BindingFlags.NonPublic | BindingFlags.Public)
					)
            {
                // See if we have a target attribute - if so, register the method as a handler.
                foreach (var att in mi.GetCustomAttributes( typeof(MediationSinkAttribute), true) )
                {
                    var msa = (MediationSinkAttribute)att;
                    var pi = mi.GetParameters();

                    Type actionType = (pi.Length != 0) 
										? GetActionType( pi.Length).MakeGenericType (
																pi.Select( p => p.ParameterType).ToArray() ) 
										: typeof(Action);

                    register (	msa.Topic, actionType,
                                   mi.IsStatic
                                   ? Delegate.CreateDelegate(actionType, mi)
                                   : Delegate.CreateDelegate(actionType, @object, mi.Name), msa.TaskMode
							);
                }
            }
        }

        public void Unregister ( object @object )
        {
            foreach ( var mi in @object.GetType().GetMethods (
												BindingFlags.Instance | BindingFlags.Static |
												BindingFlags.NonPublic | BindingFlags.Public) 
					)
            {
                foreach (var att in mi.GetCustomAttributes(typeof( MediationSinkAttribute), true))
                {
                    var msa = ( MediationSinkAttribute)att;
                    var pi = mi.GetParameters();
                    Type actionType = (pi.Length != 0) 
										? GetActionType( pi.Length).MakeGenericType (
																	pi.Select( p => p.ParameterType).ToArray() ) 
										: typeof( Action);

                    unregister (	msa.Topic, actionType,
									mi.IsStatic
									? Delegate.CreateDelegate(actionType, mi)
									: Delegate.CreateDelegate(actionType, @object, mi.Name) 
							);
                }
            }
        }


        private void register ( string key, Type actionType, Delegate handler, TaskKind taskMode )
        {
            var action = new WeakAction( handler, actionType, taskMode );

            lock ( this._handlers)
            {
                List<WeakAction> was;
                if ( this._handlers.TryGetValue(key, out was) )
                {
                    if ( was.Count > 0 )
                    {
                        WeakAction wa = was [0] ;
                        if ( wa.ActionType != actionType && !wa.ActionType.IsAssignableFrom(actionType) )
                            throw new ArgumentException ( "Invalid key passed to register handler " +
														"- existing handler has incompatible parameter type" );
                    }

                    was.Add( action);
                }
                else
                {
                    was = new List<WeakAction> { action };
                    this._handlers.Add(key, was);
                }
            }
        }

        private void unregister ( string key, Type actionType, Delegate handler )
        {
            lock ( this._handlers )
            {
                List<WeakAction> was;
                if ( this._handlers.TryGetValue( key, out was) )
                {
                    was.RemoveAll ( wa => handler == wa.GetMethod() && actionType == wa.ActionType );

                    if ( was.Count == 0 )
                        this._handlers.Remove( key);
                }
            }
        }

        private static Type GetActionType ( int n )
        {
            switch (n)
            {
                case 1: return typeof( Action<>					);
                case 2: return typeof( Action<,>				);
                case 3: return typeof( Action<,,>				);
                case 4: return typeof( Action<,,,>				);
                case 5: return typeof( Action<,,,,>				);
                case 6: return typeof( Action<,,,,,>			);
                case 7: return typeof( Action<,,,,,,>			);
                case 8: return typeof( Action<,,,,,,,>			);
                case 9: return typeof( Action<,,,,,,,,>			);
                case 10: return typeof(Action<,,,,,,,,,>		);
                case 11: return typeof(Action<,,,,,,,,,,>		);
                case 12: return typeof(Action<,,,,,,,,,,,>		);
                case 13: return typeof(Action<,,,,,,,,,,,,>		);
                case 14: return typeof(Action<,,,,,,,,,,,,,>	);
                case 15: return typeof(Action<,,,,,,,,,,,,,,>	);
                case 16: return typeof(Action<,,,,,,,,,,,,,,,>	);

                default: 
					throw new ApplicationException("Opps, Unsupported type of action") ;
            }
        }
    }

}
